/*
================================================================================
File name: tinyduino_ledboard.cpp
   System: TinyDuino LED Shield Library
 Platform: TinyDuino (Amtel ATmega328, 3.3v, 8MHz)
   Author: Madeline Usher
  Created: July 11, 2013
  Purpose: The TinyDuino LED shield from TinyCircuits has 16 Charlieplexed LEDs
           connected to digital pins 5, 6, 7, 8, and 9.  You can read more about
           the board at http://tiny-circuits.com/learn/tinyduinoled/.
================================================================================
  $LastChangedDate: 2013-07-14 21:54:08 +0000 (Sun, 14 Jul 2013) $
  $LastChangedBy: maddy314@gmail.com $
================================================================================
  Copyright (c) 2013 Madeline Usher <maddy314 ~@~ gmail.com>
  
  The TinyDuino LED Shield Library is free software: you can redistribute it
  and/or modify it under the terms of the GNU General Public License as
  published by the Free Software Foundation, either version 3 of the License, 
  or (at your option) any later version.

  This library is distributed in the hope that it will be useful, but WITHOUT 
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along with 
  this library.  If not, see <http://www.gnu.org/licenses/>.
================================================================================
*/

#include "tinyduino_ledboard.h"


////////////////////////////////////////////////////////////////////////////////
/// Constants

const byte pins[] = {5, 6, 7, 8, 9};

const LEDdesc leds[] = 
  {
   {5, 6}, {6, 5}, {5, 7}, {7, 5},
   {6, 7}, {7, 6}, {6, 8}, {8, 6},
   {5, 8}, {8, 5}, {8, 7}, {7, 8},
   {9, 7}, {7, 9}, {9, 8}, {8 ,9}
  };


////////////////////////////////////////////////////////////////////////////////
/// LED array
///
/// Describes which LEDs should be lit.  Since these are charlieplexed, only 
/// one can actually be lit at a time, so we use interrupts to quickly switch
/// between all the ON LEDs.  If it's fast enough, it will look steady to the
/// human eye.
///

bool ledStates[LEDBOARD_NUM_LEDS] = {false};


void setLED (int iLED, bool state)
 {
  ledStates[iLED] = state;
  return;
 };


void allLEDsOff ()
 {
  for (byte j = 0; j < LEDBOARD_NUM_LEDS; j++)
   {
    ledStates[j] = false;
   }
  return;
 };


void allLEDsOn ()
 {
  for (byte j = 0; j < LEDBOARD_NUM_LEDS; j++)
   {
    ledStates[j] = true;
   }
  return;
 };


////////////////////////////////////////////////////////////////////////////////
/// Low Level Pin Manipulation

inline void setPinToDisabled (byte pin)
 // Sets pin mode to INPUT, which makes the high impedence value needed to 
 // disable one of the charlieplexed lines.  Assumes this is called from within
 // an interrupt handler so it doesn't disable interrupts.
 {
  uint8_t bitMask = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  volatile uint8_t *mode = portModeRegister(port);
  volatile uint8_t *output = portOutputRegister(port);
  *mode &= ~bitMask;
  *output &= ~bitMask;
  return;
 }

inline void setPinToVCC (byte pin)
 {
  uint8_t bitMask = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  volatile uint8_t *mode = portModeRegister(port);
  volatile uint8_t *output = portOutputRegister(port);
  *mode |= bitMask;
  *output |= bitMask;
  return;
 }

inline void setPinToGnd (byte pin)
 {
  uint8_t bitMask = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  volatile uint8_t *mode = portModeRegister(port);
  volatile uint8_t *output = portOutputRegister(port);
  *mode |= bitMask;
  *output &= ~bitMask;
  return;
 }


////////////////////////////////////////////////////////////////////////////////
/// Initialization

void clearLEDpins ()
 {
  for (byte j = 0; j < LEDBOARD_NUM_PINS; j++)
   {
    pinMode(pins[j] , INPUT);
   }
  return;
 }

void initTimerInterrupt ()
 // Uses Timer 2.
 {
  noInterrupts();
  TCCR2A = 0;
  TCCR2B = (1 << CS22) | (1 << WGM22);     // F_CPU/64 and CTC mode
  TCNT2  = 0;                               // set timer to 0
  OCR2A = 156;                              // ~ 50 Hz if all 16 LEDs on
  TIMSK2 |= (1 << OCIE2A);                 // enable timer compare interrupt
  interrupts();
  return;
 }

void initTinyDuinoLEDBoard ()
 {
  clearLEDpins();
  allLEDsOff();
  initTimerInterrupt();
  return;
 }


////////////////////////////////////////////////////////////////////////////////
/// Timer Interrupt Handler

volatile byte curLED = LEDBOARD_NUM_LEDS - 1;   // which LED was last refreshed


ISR(TIMER2_COMPA_vect)          // timer compare interrupt handler
 {
  bool foundNextLED = false;
  byte nextLED = curLED;
  do
   {
    nextLED++;
    if (nextLED == LEDBOARD_NUM_LEDS)
      nextLED = 0;
    if (ledStates[nextLED])
      foundNextLED = true;
   } while ((!foundNextLED) && (nextLED != curLED));
  
  if ((!foundNextLED) || (nextLED != curLED))
   {
    // Only turn the last LED off if it's not the only one on in the array.
    setPinToDisabled(leds[curLED].vcc);
    setPinToDisabled(leds[curLED].gnd);
   }
  if (foundNextLED)
   {
    setPinToVCC(leds[nextLED].vcc);
    setPinToGnd(leds[nextLED].gnd);
    curLED = nextLED;
   }
 }
 

////////////////////////////////////////////////////////////////////////////////
/// End of Code
